# -*- coding: utf-8 -*-
# TIME   : 2023-03-04
# AUTHOR : Jinshuai Zhang
# EMAIL  : jszhanghit@163.com
from bitset import bitset
import math
def mod2div(dividend,divisor,result=0):
    """
    result:
        0: return the remainder
        1: return the quotient
        2: return the (quotient,remainder)

    the type of dividend and divisor is bitset
    """
    quotient = bitset(dividend.size()-divisor.size()+1)
    width = divisor.size()

    remainder = bitset(divisor.size(),dividend[dividend.size()-1:dividend.size()-divisor.size()])

    for i in range(dividend.size()-divisor.size()+1):
        if(remainder[width-1]):
            quotient[dividend.size()-divisor.size()-i] = bool(1)
            remainder ^= divisor
        else:
            quotient[dividend.size()-divisor.size()-i] = bool(0)

        if(i < dividend.size()-divisor.size()):
            remainder = remainder << 1
            remainder[0] = dividend[dividend.size()-divisor.size()-i-1]

    if(result==0):
        return bitset(remainder.size()-1,remainder.get_value())
    elif(result==1):
        return quotient
    else:
        return (quotient,bitset(remainder.size()-1,remainder.get_value()))




def paralle_crc(poly,K,n):
    """
    Generate the crc paralle derivation. 

    poly: 
        The poly need't contain highest bit and the poly is the hex mode.
        example: the crc16 polynomal is x^16 + x^12 + x^5 + 1 ,the poly need set up 0x1021

    K:
        The message bits.
    n:
        the crc code bits append to message.

        +--------------------------+------------+
        |      Data(K bits)        | CRC(n bits)|
        +--------------------------+------------+

    Implementation clue:
    The n bit value of F(x) is identified as a(n)
    => F(x) = a(n-1)*2**(n-1) + a(n-2)*2**(n-2) + ... + a(2)*2**2 + a(1)*2 + a(0)
    => F(x) = a(n-1)*2**(n-1) ⊕a(n-2)*2**(n-2) ⊕... ⊕a(2)*2**2 ⊕a(1)*2 ⊕a(0)

    The GF(2) field have theorem:
        F(x)%g(x) = a(n-1)*2**(n-1) % g(x) ⊕a(n-2)*2**(n-2) % g(x) ⊕... ⊕a(2)*2**2 % g(x) ⊕a(1)*2 % g(x) ⊕a(0) % g(x)

    return:
        return data is array which contain n elements, the element type is bitset(width is K). 
        Example the array is [a0,a1,...,an-1], ai is the i bit formula of CRC code.
        if the value of ai is vK-1vK-2...V0, the i bit CRC formula is vK-1*Data[K-1] ^ vK-2*Data[K-2] ^ ... ^ v1*Data[1] ^ v0*Data[0]
        For example: K = 8, the value of ai 01011001, the formula of i bit crc is Data[6] ^ Data[4] ^ Data[3] ^ Data[0]

    """
    
    bits_remainder = [0] * K

    for i in range(K):
        bits_remainder[i] = mod2div(bitset(K+n,2**(i+n)),bitset(n+1,poly+2**n))

    bits_crc = [0] * n

    for i in range(n):
        temp = bitset(K)
        for j in range(K):
            if(bits_remainder[j][i]):
                temp[j] = 0x1
        bits_crc[i] = temp

    return bits_crc


def gen_rtl_code():
    poly = int(input("Please input the polynomal in hex mode,for example 0x1021 : "),16)
    K = int(input("Please input the data width : "))
    n = int(input("Please input the crc code width : "))
    bit_poly = paralle_crc(poly,K,n)

    dw = math.ceil(math.log(K,10))
    cw = math.ceil(math.log(n,10))

    f = open("crc_"+str(n)+".v","w")

    Fx = "F(X) = X^"+str(n)
    for i in range(n):
        t = n-1-i
        temp = bitset(n,poly)
        if(temp[t]):
            Fx += " + X^"+str(t)

    module = """// +FHDR--------------------------------------------------------
// Author              : crc tool by zhangjinshuai
// Description         : 
//                       {:s}
// -FHDR--------------------------------------------------------

module crc_{:d}(
    input  logic [{:<5d}:0] i_data ,
    output logic [{:<5d}:0] o_crc
);

{:s}
endmodule
    """

    logic_code = ""

    for i in range(n):
        logic_code += ("assign o_crc[{:<" + str(cw) + "d}] = ").format(i)
        first_flag = 0
        for j in range(K):
            if(bit_poly[i][K-1-j] == True):
                if(first_flag == 0):
                    first_flag = 1
                    logic_code += ("i_data[{:<" + str(dw) +"d}]").format(K-1-j)
                else:
                    logic_code += ("^ i_data[{:<" + str(dw) + "d}]").format(K-1-j)
        if(logic_code[-2] == "="):
            logic_code += "1'b0"
        logic_code += " ;\n"
    f.write(module.format(Fx,n,K-1,n-1,logic_code))
    f.close()


gen_rtl_code()
